package com.chivan.encoder.recorder;

import android.content.Context;
import android.media.AudioFormat;
import android.media.MediaRecorder;
import android.text.TextUtils;

import com.chivan.encoder.Mp3Encoder;
import com.chivan.encoder.log.Logcat;

import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;

/*
 * Create by weslywang on 2019-11-06
 * Copyright © Tencent
 *
 * Description:
 */

public abstract class BaseAudioRecorder {
    protected int audioSource = MediaRecorder.AudioSource.MIC;// 音频获取源
    protected final int sampleRate = 16000;//采样率
    protected final int channel = AudioFormat.CHANNEL_IN_MONO;// 设置音频的录制的声道CHANNEL_IN_STEREO为双声道，CHANNEL_CONFIGURATION_MONO为单声道
    protected final int audioFormat = AudioFormat.ENCODING_PCM_16BIT; // 音频数据格式:PCM 16位每个样本。保证设备支持。PCM 8位每个样本。不一定能得到设备支持。

    private int maxDuration = 120;//最大录制时长 单位：秒
    private final int audioTimeSpanLength = 200;//音频数据分片大小: 200毫秒
    private final int vadTimeSpanLength = 10;//vad分片大小: 10毫秒（vad的算法要求输入数据分片是10毫秒的整数倍）
    private final int levelTimeSpanLength = 100;//计算音量的分片大小: 100毫秒
    // 采样率16000*格式2Byte(16bit)/5(1秒=5*200毫秒)=6400
    protected final int MIN_BYTE_LEN_200_ms = sampleRate * audioFormat / (1000 / audioTimeSpanLength);//6400字节，200毫秒内能产生多少byte数据,speex 以时间窗口进行压缩，不然会出错
    protected final int COMPRESSION_MIN_SHORT_LEN = MIN_BYTE_LEN_200_ms / 2;
    protected final int MIN_BYTE_LEN_10_ms = sampleRate * audioFormat / (1000 / vadTimeSpanLength);//320字节，10毫秒内能产生多少byte数据,vad的算法要求输入数据分片是10毫秒的整数倍
    protected final int MIN_BYTE_LEN_100_ms = sampleRate * audioFormat / (1000 / levelTimeSpanLength);//3200字节，100毫秒内能产生多少byte数据
    private int MIN_BYTE_LEN_max_duration_ms = sampleRate * audioFormat * maxDuration;//最大录制时长能产生多少byte数据

    protected int bufferSize = 1024;

    private long startTime, endTime, recordTime;
    private Context mContext;
    private boolean mIsWorking = false;//录音机是否正在录音
    private Mp3Encoder mp3Encoder;
    private String mAudioFilePath;
    private final static String DEFAULT_FILE_NAME = "qt_record_file";
    private static String DEFAULT_RAW_FOLDER;
    private IQTAudioRecorderCallback callback;
    private CacheManager cacheManager;

    Constant.RecorderAudioFormat dataEncoder = Constant.RecorderAudioFormat.not_use;
    Constant.RecorderAudioFormat fileEncoder = Constant.RecorderAudioFormat.not_use;

    public BaseAudioRecorder(Context context) {
        Logcat.i("BaseAudioRecorder 初始化！！！");
        this.mContext = context;
        mp3Encoder = new Mp3Encoder();
        mAudioFilePath = FileUtil.getLocalDirPath(context);
        DEFAULT_RAW_FOLDER = FileUtil.getLocalDirPath(context);//默认的录音数据保存路径
    }

    public void setAudioFilePath(String path) {
        if (TextUtils.isEmpty(path)) {
            return;
        }

        mAudioFilePath = path;
    }

    public void setRecorderCallback(IQTAudioRecorderCallback callback) {
        this.callback = callback;
    }

    public void setMaxDuration(int maxDuration) {
        this.maxDuration = maxDuration;
        MIN_BYTE_LEN_max_duration_ms = sampleRate * audioFormat * maxDuration;
    }

    public void setDataEncoder(Constant.RecorderAudioFormat dataEncoder) {
        this.dataEncoder = dataEncoder;
    }

    public void setFileEncoder(Constant.RecorderAudioFormat fileEncoder) {
        this.fileEncoder = fileEncoder;
    }

    public boolean startRecording() {
        if (mIsWorking) {
            stopRecording();
        }
        if (fileEncoder == Constant.RecorderAudioFormat.mp3) {
            mp3Encoder.initMp3Encoder(1, 28, sampleRate, 5);
        }

        long arsBegin = System.currentTimeMillis();
        try {
            Logcat.i("启动录音机");
            int re = startNativeRecorder();
            Logcat.i("打开录音机:" + re);
            if (re != 0) {
                return false;
            }
            if (callback != null) {
                callback.onSendRecordStart();
            }
        } catch (Exception e) {
            Logcat.e("startRecording fail!!!!!!!!!!!!!" + e.toString());
            return false;
        }
        long arsDuration = System.currentTimeMillis() - arsBegin;
        Logcat.i("录音机启动录音耗时" + arsDuration);

//        timerTask();
        startTime = System.currentTimeMillis();
        mIsWorking = true;
        // 开启音频文件写入线程
        new Thread(new AudioRecordThread()).start();
        return true;
    }

    //打开本地录音机
    abstract protected int startNativeRecorder();

    //读取录音机数据
    protected abstract int readNativeRecorderData(byte[] data);

    //是否需要speex压缩
    private boolean isSupportSpeex() {
        return dataEncoder == Constant.RecorderAudioFormat.speex_wb_7;
    }

    /**
     * 保存录音分片数据
     *
     * @param index
     * @param audiodata
     * @param enableSpeex
     * @param flag
     * @param dataLength  640 的整数倍
     */
    public void saveAuditFragment(int index, byte[] audiodata, boolean enableSpeex, String flag, int dataLength) {
        String uuid = "";
        if (dataLength <= 0) {
            return;
        }
        File dir = new File(DEFAULT_RAW_FOLDER);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        dir = new File(DEFAULT_RAW_FOLDER + File.separator + uuid);
        if (dir.exists()) {
            dir.delete();
        }
        dir.mkdirs();
        FileOutputStream fosRaw = null;
//        String fileFlag = enableSpeex ? "speex" : "raw";
        String fileName = String.format("%s%s_%s_%d.seq", DEFAULT_RAW_FOLDER + File.separator + uuid + File.separator, uuid, flag, index);
        try {
            File fileRaw = new File(fileName);

            if (fileRaw.exists()) {
                fileRaw.delete(); //删除上一次的录音文件
            }
            fosRaw = new FileOutputStream(fileRaw);// 建立一个可存取字节的文件
            Logcat.i("保存分片：数组长度=" + audiodata.length + ",实际长度=" + dataLength + "," + fileName);
            byte[] encodeData = null;
            if (enableSpeex) {
//                encodeData = encodeAudioData(audiodata, dataLength);
            }
            if (enableSpeex) {
                fosRaw.write(encodeData);//压缩之后的数据全部都是有效数据，直接写入文件
            } else {
                fosRaw.write(audiodata, 0, dataLength);//只写入有效数据字节
            }
        } catch (Exception e) {
            Logcat.e("create audio fragment file has exception " + e.toString());
        } finally {
            // 关闭写入流
            if (null != fosRaw) {
                try {
                    fosRaw.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 录音机缓存管理
     */
    private class CacheManager {
        private int cacheAuditDataLen;//缓存数据的有效数据长度
        private final int cacheNum = 2;//缓存池个数
        private int currentCacheIndex;//当前采用的缓存池
        private ArrayList<byte[]> cacheStore;
        private int totalSendDataLen = 0;//已经发送的数据byte
        private int sequence = 0;//当前处理的音频分片索引
        private FileOutputStream fosSpeex = null;
        private FileOutputStream fosMp3 = null;
        private FileOutputStream fosPcm = null;
        private boolean isTailPieceSend = false;//尾包是否已发送

        public CacheManager() {
            cacheAuditDataLen = 0;
            currentCacheIndex = 0;
            cacheAuditDataLen = 0;
            cacheStore = new ArrayList<>();
            for (int i = 0; i < cacheNum; i++) {
                byte[] cacheAuditData = new byte[bufferSize * 2];//录音缓存数据
                cacheStore.add(cacheAuditData);
            }
        }

        public void setOutputStream(FileOutputStream fosSpeex, FileOutputStream fosPcm, FileOutputStream fosMp3) {
            this.fosSpeex = fosSpeex;
            this.fosPcm = fosPcm;
            this.fosMp3 = fosMp3;
        }

        public void add(byte[] audiodata, int length) {
            if (length <= 0) {
                return;
            }
            byte[] cache = cacheStore.get(currentCacheIndex);
            System.arraycopy(audiodata, 0, cache, cacheAuditDataLen, length);
            int start = cacheAuditDataLen;
            cacheAuditDataLen += length;
            Logcat.i("编号：" + currentCacheIndex + ", 添加缓存音频分片：开始索引=" + start + "，长度=" + length + "，当前缓存数据总长度=" + cacheAuditDataLen);
            try {
                dispatch();
            } catch (Exception e) {
                Logcat.w("录音分片分发异常：" + e.getMessage());
            }
        }

        //处理音频数据
        public void dispatch() {
            synchronized (this) {
                byte[] cache = cacheStore.get(currentCacheIndex);
                // 分发音频数据
                if (cacheAuditDataLen >= MIN_BYTE_LEN_200_ms) {//（200毫秒一个分片进行处理）
                    ArrayList<Integer> vadStore = new ArrayList<>();//存储所有计算好的vad

                    byte[] piece = new byte[MIN_BYTE_LEN_200_ms];
                    System.arraycopy(cache, 0, piece, 0, MIN_BYTE_LEN_200_ms);
                    cacheAuditDataLen = cacheAuditDataLen - MIN_BYTE_LEN_200_ms;
                    Logcat.i("编号：" + currentCacheIndex + "，读取缓存数据：读取长度=" + MIN_BYTE_LEN_200_ms + "，缓存中待处理长度：" + cacheAuditDataLen);
                    currentCacheIndex = (currentCacheIndex + 1) % cacheNum;//切换缓存区
                    byte[] nextCache = cacheStore.get(currentCacheIndex);
                    // 把剩余数据拷贝到下个缓冲区
                    if (cacheAuditDataLen > 0) {
                        System.arraycopy(cache, MIN_BYTE_LEN_200_ms, nextCache, 0, cacheAuditDataLen);
                        Logcat.i("编号：" + currentCacheIndex + "，迁入剩余缓存数据：迁入数据长度=" + cacheAuditDataLen);
                    }
                    int sendDataLength = MIN_BYTE_LEN_200_ms;
                    if (totalSendDataLen + MIN_BYTE_LEN_200_ms > MIN_BYTE_LEN_max_duration_ms) {//如果发送出去之后数据总长度就大于最大录制时长，就进行截断
                        sendDataLength = MIN_BYTE_LEN_max_duration_ms - totalSendDataLen;
                        byte[] needSend = new byte[sendDataLength];
                        System.arraycopy(cache, 0, needSend, 0, sendDataLength);
                        piece = needSend;
                    }
                    totalSendDataLen += sendDataLength;
                    if (totalSendDataLen >= MIN_BYTE_LEN_max_duration_ms) {
                        isTailPieceSend = true;
                    }
                    dispatchAuditData(piece, sendDataLength, sequence, fosSpeex, fosPcm, fosMp3, vadStore, isTailPieceSend);
                    sequence++;
                    if (isTailPieceSend) {
                        stopRecording();
                        if (callback != null) {
                            callback.onSendRecordStop();
                        }
                        Logcat.i("自动停止录音：发送音频总长度=" + totalSendDataLen);
                    }
                } else if (!mIsWorking) {//处理剩余数据
                    if (cacheAuditDataLen > 0) {
                        byte[] piece = new byte[cacheAuditDataLen];
                        System.arraycopy(cache, 0, piece, 0, cacheAuditDataLen);
                        Logcat.i("编号：" + currentCacheIndex + "，读取全部剩余缓存数据：读取长度=" + cacheAuditDataLen);
                        // 处理vad计算
                        ArrayList<Integer> vadStore = new ArrayList<>();//存储所有计算好的vad
                        dispatchAuditData(piece, cacheAuditDataLen, sequence, fosSpeex, fosPcm, fosMp3, vadStore, true);
                        cacheAuditDataLen = 0;
                        isTailPieceSend = true;
                    } else if (!isTailPieceSend) {
                        byte[] piece = new byte[MIN_BYTE_LEN_200_ms];
                        Logcat.i("发送空数据尾包");
                        isTailPieceSend = true;
                        dispatchAuditData(piece, MIN_BYTE_LEN_200_ms, sequence, null, null, null, new ArrayList<Integer>(), true);
                    }
                }
            }
        }
    }

    public String getDefaultMp3Path() {
        return DEFAULT_RAW_FOLDER + File.separator + DEFAULT_FILE_NAME + ".mp3";
    }

    /**
     * 这里将数据写入文件，但是并不能播放，因为AudioRecord获得的音频是原始的裸音频，
     * 如果需要播放就必须加入一些格式或者编码的头信息。但是这样的好处就是你可以对音频的 裸数据进行处理，比如你要做一个爱说话的TOM
     * 猫在这里就进行音频的处理，然后重新封装 所以说这样得到的音频比较容易做一些音频的处理。
     */
    class AudioRecordThread implements Runnable {
        long preAudioData = -1;//前一个pcm分片的接收时间

        @Override
        public void run() {
            byte[] audiodataTmp = new byte[bufferSize];
            //录音机读取数据的ID
            int recodeIndex = 0;
            int readsize = 0;
            cacheManager = new CacheManager();
            FileOutputStream fosSpeex = null;
            FileOutputStream fosPcm = null;
            FileOutputStream fosMp3 = null;
            String pcmFilePath = DEFAULT_RAW_FOLDER + File.separator + DEFAULT_FILE_NAME + ".pcm";
            String speexFilePath = DEFAULT_RAW_FOLDER + File.separator + DEFAULT_FILE_NAME + ".speex";
            String mp3FilePath = getDefaultMp3Path();
//            String wavFilePath = DEFAULT_RAW_FOLDER + File.separator + DEFAULT_FILE_NAME + ".wav";
            try {
                File fileSpeex = new File(speexFilePath);

                if (fileSpeex.exists()) {
                    fileSpeex.delete(); //删除上一次的录音文件
                }
                fosSpeex = new FileOutputStream(fileSpeex);// 建立一个可存取字节的文件

                //创建pcm音频文件
                File filePcm = new File(pcmFilePath);

                if (filePcm.exists()) {
                    filePcm.delete(); //删除上一次的录音文件
                }
                fosPcm = new FileOutputStream(filePcm);// 建立一个可存取字节的文件

                File fileMp3 = new File(mp3FilePath);
                if (fileMp3.exists()) {
                    fileMp3.delete();
                }
                fosMp3 = new FileOutputStream(fileMp3);
            } catch (Exception e) {
                Logcat.e("create audio file has exception " + e.toString());
            }
            cacheManager.setOutputStream(fosSpeex, fosPcm, fosMp3);
            AudioFocusUtil.requestAudioFocus(mContext);
            while (mIsWorking) {
                try {
                    readsize = readNativeRecorderData(audiodataTmp);
                    if (preAudioData != -1) {
                        long currentAudioData = System.currentTimeMillis();
                        long dif = currentAudioData - preAudioData;
                        Logcat.i("diff=" + dif);
                    }
                    preAudioData = System.currentTimeMillis();
                } catch (Exception e) {
                    Logcat.e(e.toString());
                }
                if (readsize > 0) {
                    cacheManager.add(audiodataTmp, readsize);
                    recodeIndex++;
                }
//                saveAuditFragment(recodeIndex, audiodata, true, "speex", readsize);
//                saveAuditFragment(recodeIndex, audiodata, false, "raw", readsize);
            }
            AudioFocusUtil.abandonAudioFocus(mContext);
            if (callback != null) {
                callback.onSendRecordStop();
            }
            // 关闭写入流
            if (null != fosSpeex) {
                try {
                    fosSpeex.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            // 关闭写入流
            if (null != fosMp3) {
                try {
                    fosMp3.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            // 关闭写入流
            if (null != fosPcm) {
                try {
                    fosPcm.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (fileEncoder == Constant.RecorderAudioFormat.wav) {
                Logcat.i("wavFilePath=" + mAudioFilePath);
                FileUtil.copyWaveFile(sampleRate, channel, bufferSize, pcmFilePath, mAudioFilePath);//给裸数据加上头文件
            }
        }
    }

    //处理读取到的音频数据
    private void dispatchAuditData(byte[] audioData, int readsize, int recodeIndex, FileOutputStream fosSpeex, FileOutputStream fosPcm, FileOutputStream fosMp3, List<Integer> vadData, boolean isEnd) {
        boolean audioSupportSpeex = isSupportSpeex();
        byte[] encodeData = new byte[MIN_BYTE_LEN_200_ms];
        byte[] encodeMp3Data;
        if (readsize > 0 && fosPcm != null) {
            writeAudioToFile(fosPcm, audioData, readsize);
        }
        Logcat.d("处理数据：readsize=" + readsize);
        if (fileEncoder == Constant.RecorderAudioFormat.mp3) {
            if (readsize > 0 && fosMp3 != null) {
                encodeMp3Data = mp3Encoder.encodeAudioData(audioData, readsize);
                if (encodeMp3Data != null) {
                    writeAudioToFile(fosMp3, encodeMp3Data, encodeMp3Data.length);
                }
            }
        }
    }

    private void writeAudioToFile(FileOutputStream fos, final byte[] audiodata, final int dataLength) {
        if (null == fos || null == audiodata || dataLength <= 0)
            return;
        try {
            fos.write(audiodata, 0, dataLength);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //停止本地录音机
    protected abstract String stopNativeRecorder();

    public void stopRecording() {
        if (mIsWorking) {
            mIsWorking = false;
            endTime = System.currentTimeMillis();
            recordTime = endTime - startTime;
            stopNativeRecorder();
            if (cacheManager != null) {
                cacheManager.dispatch();//发送剩余数据
            }
            if (fileEncoder == Constant.RecorderAudioFormat.mp3) {
                mp3Encoder.destroyMp3Encoder();
            }
        }
    }
}